package com.xt.base.dao.hibernate.userType;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

import com.xt.base.util.Compressor;

public class TimestampableMap implements Map<String,String>,Serializable,UserType{

	private static final long serialVersionUID = -5609352683021193129L;

	private Map<String, String> target;

	private static String splitter = "\u000c";

	private static int maxTestTimes = 30;

	public TimestampableMap() {
		this.target = new HashMap<String, String>();
	}

	public TimestampableMap(TimestampableMap map) {
		this.target = new HashMap<String, String>(map.target);
	}

	public TimestampableMap(Map<String, String> map) {
		if (TimestampableMap.class.isInstance(map))
			this.target = ((TimestampableMap) map).target;
		else if (isTimestaped(map))
			this.target = new HashMap<String, String>(map);
		else {
			this.target = new HashMap<String, String>();
			if (map != null)
				this.putAll(map);
		}
	}

	private boolean isTimestaped(Map<String, String> map) {
		if (TimestampableMap.class.isInstance(map))
			return true;
		int i = 0;
		for (String key : map.keySet()) {
			if (!StringUtils.isEmpty(getTimestampValue(map.get(key))))
				return true;
			if (++i > maxTestTimes)
				break;
		}
		return false;
	}

	public void clear() {
		target.clear();
	}

	public boolean containsKey(Object key) {
		if (key == null)
			return false;
		return target.containsKey(key);
	}

	private String removeTimestamp(String value) {
		if (!StringUtils.isEmpty(value))
			return StringUtils.split(value, splitter)[0];
		return null;
	}

	private String sealTimestamp(String value) {
		if (!StringUtils.isEmpty(value))
			return value + splitter + System.currentTimeMillis();
		return null;
	}

	private String getTimestampValue(String value) {
		String timestamp = null;
		if (!StringUtils.isEmpty(value)) {
			String[] v = StringUtils.split(value, splitter);
			if (v.length > 1)
				timestamp = v[1];
		}
		return timestamp;
	}

	public String getTimestamp(String key) {
		return getTimestampValue(target.get(key));
	}

	public boolean containsValue(Object value) {
		if (value == null)
			return false;
		for (String v : target.values())
			if (value.equals(removeTimestamp(v)))
				return true;
		return false;
	}

	private class SEntry implements java.util.Map.Entry<String, String> {

		private java.util.Map.Entry<String, String> entity;

		private SEntry(java.util.Map.Entry<String, String> entity) {
			this.entity = entity;
		}

		public String getKey() {
			return entity.getKey();
		}

		public String getValue() {
			return removeTimestamp(entity.getValue());
		}

		public String setValue(String value) {
			if (StringUtils.isEmpty(value))
				return null;
			return entity.setValue(sealTimestamp(value));
		}

	}

	public Set<java.util.Map.Entry<String, String>> entrySet() {
		Set<java.util.Map.Entry<String, String>> eSet = new HashSet<java.util.Map.Entry<String, String>>();
		for (java.util.Map.Entry<String, String> e : target.entrySet())
			eSet.add(new SEntry(e));
		return eSet;
	}

	public String get(Object key) {
		return removeTimestamp(target.get(key));
	}

	public boolean isEmpty() {
		return target.isEmpty();
	}

	public Set<String> keySet() {
		return target.keySet();
	}

	public String put(String key, String value) {
		return put(key, sealTimestamp(value),true);
	}

	private String put(String key, String value,boolean seal) {
		if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value))
			return null;
		return target.put(key, seal?sealTimestamp(value):value);
	}

	public void putAll(Map<? extends String, ? extends String> t) {
		boolean seal = TimestampableMap.class.isInstance(t);
		for (String key : t.keySet())
			put(key, !seal?sealTimestamp(t.get(key)):t.get(key));
	}

	public String remove(Object key) {
		return target.remove(key);
	}

	public int size() {
		return target.size();
	}

	public Collection<String> values() {
		List<String> list = new ArrayList<String>();
		for (String s : target.values())
			list.add(removeTimestamp(s));
		return list;
	}

	public Map<String, String> getTarget() {
		return Collections.unmodifiableMap(target);
	}
	
	private static final String SPLITTER1 = "\u000f",SPLITTER2 = "\u000e";   

	public Object deepCopy(Object value) throws HibernateException {
        return value; 
	}

	public boolean equals(Object x, Object y) throws HibernateException {
/*        if (x == y)   
            return true;   */
        /*if (x != null && y != null){
        	TimestampableMap mx = (TimestampableMap)x;
        	TimestampableMap my = (TimestampableMap)y;
        	if(mx.size() != my.size())return false;
        	for(String key:mx.keySet()){
        		String xv = mx.get(key),yv = my.get(key);
        		if(xv == null || yv == null) return false;
        		if(!xv.equals(yv)) return false;
        	}
            return true; 
        }*/
        return false;   
	}
	public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
		byte[]  value = (byte[]) Hibernate.BINARY.nullSafeGet(rs, names[0]);   
        if (value != null) {   
            return parse(value);   
        } else {   
            return null;   
        }   
	}

	public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
        if (value != null) {   
        	byte[] str = assemble((TimestampableMap) value);   
            Hibernate.BINARY.nullSafeSet(st, str, index);   
        } else {   
            Hibernate.BINARY.nullSafeSet(st, value, index);   
        }   
	}

	public Class<?> returnedClass() {
		return Map.class;
	}

	public int[] sqlTypes() {
		return new int[] { Types.BLOB };
	}

	private byte[] assemble(TimestampableMap map){
		if(map == null || map.size() == 0 )return null;
        StringBuffer strBuf = new StringBuffer();
        int i = 1,size = map.size();
        for(String key:map.keySet()){
        	strBuf.append(key).append(SPLITTER2).append(map.get(key));
        	if(i<size){
        		strBuf.append(SPLITTER1);
        		i++;
        	}
        }
   		return Compressor.zipArray(strBuf.toString());
	}
		
    private Map<String,String> parse(byte[]  input) {
    	String value = Compressor.unzip(input);
    	if(StringUtils.isEmpty(value))return null;
     	String nativestr = "_temp_by_native_code";
       	if(value.startsWith(SPLITTER1))value = nativestr + value;
       	if(value.endsWith(SPLITTER1))value += nativestr;
        String[] strs = StringUtils.split(value, SPLITTER1);
        TimestampableMap map = new TimestampableMap();   
        for (int i = 0; i < strs.length; i++){
        	String addstr = strs[i];
        	addstr = nativestr.equals(addstr)?"":addstr;
        	String[] keyValue = StringUtils.split(addstr, SPLITTER2);
        	if(keyValue.length > 0){
 	        	if(keyValue.length > 1){
	        		map.put(parseNull(keyValue[0]), parseNull(keyValue[1]),false);
	        	}else map.put(parseNull(keyValue[0]), null);
        	}
         }
        return map;   
    } 
    
	private String parseNull(String str){
		if("null".equals(str) || str == null)return "";
		return str;
	}

	public Object assemble(Serializable arg0, Object arg1) throws HibernateException {
		return arg0;
	}

	public Serializable disassemble(Object arg0) throws HibernateException {
		return (Serializable)arg0;
	}

	public int hashCode(Object arg0) throws HibernateException {
		return arg0.hashCode();
	}

	public boolean isMutable() {
		return false;
	}

	public Object replace(Object arg0, Object arg1, Object arg2) throws HibernateException {
		return arg0;
	}
}
